package gu.sql2java;

import static com.google.common.base.Preconditions.*;
import static com.google.common.base.MoreObjects.*;
import static gu.sql2java.SimpleLog.*;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.ServiceConfigurationError;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.alibaba.fastjson.serializer.ObjectSerializer;
import com.gitee.l0km.common.spring.core.annotation.AnnotationUtils;
import com.gitee.l0km.common.spring.core.annotation.MergedAnnotation;
import com.gitee.l0km.common.spring.core.annotation.TypeMappedAnnotations;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Collections2;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import com.google.common.primitives.Ints;
import com.google.common.util.concurrent.UncheckedExecutionException;

import gu.sql2java.annotations.ColumnCodecConfig;
import gu.sql2java.annotations.RowMeta;
import gu.sql2java.utils.SPIUtils;
import com.alibaba.fastjson.annotation.JSONField;

import static gu.sql2java.BaseTypeColumnCodec.BASE_CODEC;
import static gu.sql2java.BaseTypeColumnCodec.isBaseColumnType;
import static gu.sql2java.BaseTypeColumnCodec.isJdbcType;
import static gu.sql2java.Sql2javaSupport.findSubClass;

/**
 * meta data used to define a table
 * @author guyadong
 *
 */
public class RowMetaData implements IRowMetaData{
	protected static final String UNKNOW_TABLENAME="UNKNOWN";
	protected static final String UNKNOW_TABLETYPE="UNKNOWN";
	public final String tablename;
	public final String tableType;
	public final Class<? extends BaseBean> beanType;
	public final String coreClass;
	public final Class<? extends TableManager<?>> managerInterfaceClass;
	public final String alias;
	public final ImmutableList<String> columnNames;
	/**
	 * @since 3.18.0 
	 */
	public final ImmutableList<String> columnTypeNames;
	public final String columnFields;
	public final String columnFullFields;
	public ImmutableList<String> columnFullFieldList;
    public final ImmutableList<String> columnJavaNames;
	public final ImmutableList<Method> getterMethods;
	public final ImmutableList<Method> setterMethods;
	/**
	 * @since 3.18.0 
	 */
	public final List<Method> readMethods;
	/**
	 * @since 3.18.0 
	 */
	public final List<Method> writeMethods;
	public final ImmutableList<Class<?>> columnTypes;
	/**
	 * @since 3.18.1
	 */
	public final ImmutableList<Class<?>> fieldTypes;
	/**
	 * @since 3.21.0
	 */
	public final ImmutableList<Class<?>> jdbcTypes;
	private final ImmutableMap<String, Integer> nameIndexsMap;
	private final ImmutableMap<String, Integer> javaNameIndexsMap;
	public final int[] defaultColumnIdList;
	public final int[] columnSizes;
	public final int[] sqlTypes;
	public final ImmutableMap<String, Class<?>> typesMap;

	public final int columnCount;
	public final int[] primaryKeyIds;
	public final String[] primaryKeyNames;
	public final int primaryKeyCount;
	public final Class<?>[] primaryKeyTypes;

	/** lazy load */
	private volatile ImmutableMap<String, Object[]> junctionTablePkMap;
	private final Map<String, String> junctionTablePkStrMap;
	public final Class<?> lockColumnType;
	public final String lockColumnName;
	/**
	 * tablename-ForeignKeyMetaData map
	 */
	public final Map<String, ForeignKeyMetaData> foreignKeys;
	/**
	 * universal name-ForeignKeyMetaData map
	 */
	public final Map<String, ForeignKeyMetaData> foreignKeysRn;
	private final List<String> importedFknames;
	public final Map<String, IndexMetaData> indices;
	public final Map<String, IndexMetaData> indicesRn;
	/**
	 * 是否有自动更新列
	 * <ul>
	 * <li>true: 有自动更新列</li>
	 * <li>false: 无自动更新列</li>
	 * <li>null: 未知</li>
	 * </ul>
	 * @since 3.34.0
	 */
	public final Boolean hasAutoUpdateColumn;
	public final Function<String, Integer> COLUMNID_FUN = new Function<String, Integer>(){

		@Override
		public Integer apply(String input) {
			return columnIDOf(input);
		}};
	public final Function<Integer,String> COLUMNNAME_FUN = new Function<Integer,String>(){
		
		@Override
		public String apply(Integer input) {
			return columnNameOf(input);
		}};
	public final Function<String, Class<?>> COLUMNTYPE_FUN = new Function<String, Class<?>>(){

		@Override
		public Class<?> apply(String input) {
			return columnTypeOf(input);
		}};
	/**
	 * @since 3.22.0
	 */
	public final Function<String, Class<?>> JDBCTYPE_FUN = new Function<String, Class<?>>(){
		
		@Override
		public Class<?> apply(String input) {
			return jdbcTypeOf(columnIDOf(input));
		}};
	/** lazy load */
	private volatile ImmutableList<ForeignKeyMetaData> importedKeys;
	/** lazy load */
	private volatile Map<String, ForeignKeyMetaData> importKeysMap;
	public final int autoincrementColumnId;
	private volatile Optional<TableManager<? extends BaseBean>> manager;
	/**
	 * @since 3.21.0
	 */
	public final ColumnCodec[] columnCodecs;
	/**
	 * @since 3.27.0
	 */
	public final ObjectSerializer[] columnJsonSerializers;
	private volatile RowMeta rowMetaAnnot;
	private volatile Set<Integer> primaryKeyIdSet;
	/**
	 * @since 3.32.0
	 */
	public final ImmutableList<ColumnVisibility> visibilities;
	protected RowMetaData(
			String tablename,
			String tableType,
			Class<? extends BaseBean> beanType, 
			String coreClass,
			Class<? extends TableManager<?>> managerInterfaceClass, 
			String alias,
			List<String> columnNames, 
			List<String> columnTypeNames, 
			List<String> columnJavaNames, 
			List<String> getters, 
			List<String> setters, 
			Class<?>[] columnTypes, 
			int[] columnSizes, 
			int[] sqlTypes, 
			List<String> primaryKeyNames, 
			Map<String, String> junctionTablePkMap, 
			Class<?> lockColumnType, 
			String lockColumnName, 
			List<String> foreignKeys, 
			List<String> importedFknames, 
			List<String> indices, 
			String autoincrement, 
			Boolean hasAutoUpdateColumn) {
		columnJavaNames = firstNonNull(columnJavaNames,Collections.<String>emptyList());
		getters = firstNonNull(getters,Collections.<String>emptyList());
		setters = firstNonNull(setters,Collections.<String>emptyList());		
		this.junctionTablePkStrMap = firstNonNull(junctionTablePkMap, Collections.<String,String>emptyMap());
		primaryKeyNames = firstNonNull(primaryKeyNames, Collections.<String>emptyList());
		foreignKeys = firstNonNull(foreignKeys, Collections.<String>emptyList());
		indices = firstNonNull(indices, Collections.<String>emptyList());
		autoincrement = firstNonNull(autoincrement, "");
		this.tablename = checkNotNull(tablename,"tablename is null");
		this.tableType = checkNotNull(tableType,"tableType is null");
		this.beanType = checkNotNull(beanType,"beanType is null");
		this.coreClass = coreClass;
		this.managerInterfaceClass = managerInterfaceClass;
		this.alias = Strings.emptyToNull(alias);
		this.columnNames = ImmutableList.copyOf(checkNotNull(columnNames,"columnNames is null"));
		this.columnTypeNames = ImmutableList.copyOf(checkNotNull(columnTypeNames,"columnTypeNames is null"));
		this.columnJavaNames = ImmutableList.copyOf(columnJavaNames);
		this.columnTypes = ImmutableList.copyOf(checkNotNull(columnTypes,"columnTypes is null"));
		this.columnSizes = null == columnSizes ? null : Arrays.copyOf(columnSizes,columnSizes.length);
		this.sqlTypes = Arrays.copyOf(checkNotNull(sqlTypes,"sqlTypes is null"),sqlTypes.length);
		checkArgument(this.columnNames.size() == this.columnTypes.size() && this.columnTypes.size() == this.sqlTypes.length,
				"MISMATCH LENGTH for input list");
		checkArgument(this.columnJavaNames.isEmpty() || this.columnJavaNames.size() == this.sqlTypes.length,
				"MISMATCH LENGTH for columnJavaNames");
		checkArgument(getters.isEmpty() || getters.size() == this.sqlTypes.length,
				"MISMATCH LENGTH for getters");
		checkArgument(setters.isEmpty() || setters.size() == this.sqlTypes.length,
				"MISMATCH LENGTH for setters");
		
		ImmutableMap.Builder<String, Integer> nameIndexBuilder = ImmutableMap.builder();
		ImmutableMap.Builder<String, Integer> javaNameIndexBuilder = ImmutableMap.builder();
		ImmutableMap.Builder<String, Class<?>> nameTypeBuilder = ImmutableMap.builder();
		ImmutableList.Builder<Class<?>> fieldTypeBuilder = ImmutableList.builder();
		this.defaultColumnIdList =  new int[sqlTypes.length];
		for(int i = 0; i < sqlTypes.length; ++i){
			defaultColumnIdList[i] = i;
			nameIndexBuilder.put(columnNames.get(i), i);
			nameTypeBuilder.put(columnNames.get(i), columnTypes[i]);
			if(!columnJavaNames.isEmpty()){
				javaNameIndexBuilder.put(columnJavaNames.get(i), i);
				try {
					fieldTypeBuilder.add(beanType.getDeclaredField(columnJavaNames.get(i)).getType());
				} catch (NoSuchFieldException | SecurityException e) {
					throw new RuntimeException(e);
				}
			}
		}
		columnFields = Joiner.on(",").join(columnNames);
		columnFullFieldList = ImmutableList.copyOf(Lists.transform(columnNames, new Function<String,String>(){

            @Override
            public String apply(String input) {
                if("UNKNOWN".equals(RowMetaData.this.tableType)){
                    return input;
                }
                return RowMetaData.this.tablename + "." + input;
            }}));
		columnFullFields = Joiner.on(",").join(columnFullFieldList);
			
		this.nameIndexsMap = nameIndexBuilder.build();
		this.autoincrementColumnId = firstNonNull(nameIndexsMap.get(autoincrement), -1).intValue();

		this.javaNameIndexsMap = javaNameIndexBuilder.build();
		this.typesMap = nameTypeBuilder.build();
		this.fieldTypes = fieldTypeBuilder.build();
		this.columnCount = sqlTypes.length;
		this.primaryKeyNames = primaryKeyNames.toArray(new String[0]);
		
		this.primaryKeyCount = primaryKeyNames.size();
		this.primaryKeyIds = new int[primaryKeyNames.size()];
		for(int i = 0 ; i < primaryKeyIds.length; ++i){
			String name = primaryKeyNames.get(i);
			checkArgument(primaryKeyIds[i]>=0,"INVALID primary key name %s",name);
			primaryKeyIds[i] = columnIDOf(name);
		}
		this.primaryKeyTypes = Lists.transform(primaryKeyNames, COLUMNTYPE_FUN).toArray(new Class<?>[0]);
		this.lockColumnType = lockColumnType;
		this.lockColumnName = lockColumnName;
		LinkedHashMap<String,ForeignKeyMetaData> fkBuilder = Maps.newLinkedHashMap();
		LinkedHashMap<String,ForeignKeyMetaData> fkRnameBuilder = Maps.newLinkedHashMap();
		for(String fk:foreignKeys){
			ForeignKeyMetaData data = new ForeignKeyMetaData(fk, tablename);
			fkBuilder.put(data.name,data);
			fkRnameBuilder.put(data.readableName,data);
		}
		this.foreignKeys = Collections.unmodifiableMap(fkBuilder);
		this.foreignKeysRn = Collections.unmodifiableMap(fkRnameBuilder);
		
		this.importedFknames = Collections.unmodifiableList(firstNonNull(importedFknames,Collections.<String>emptyList()));		
		
		LinkedHashMap<String,IndexMetaData> indexBuilder = Maps.newLinkedHashMap();
		LinkedHashMap<String,IndexMetaData> indexRnameBuilder = Maps.newLinkedHashMap();
		for(String fk:indices){
			IndexMetaData data = new IndexMetaData(fk, tablename);
			indexBuilder.put(data.name,data);
			indexRnameBuilder.put(data.readableName,data);
		}
		this.indices = Collections.unmodifiableMap(indexBuilder);
		this.indicesRn = Collections.unmodifiableMap(indexRnameBuilder);
		ImmutableList.Builder<Method> getterMethodBuilder = ImmutableList.builder();
		ImmutableList.Builder<Method> setterMethodBuilder = ImmutableList.builder();
		List<Method> readMethodBuffer = Lists.newArrayList();
		List<Method> writeMethodBuffer = Lists.newArrayList();
		ImmutableList.Builder<Class<?>> jdbcTypeBuilder = ImmutableList.builder();

		for(int i = 0; i < sqlTypes.length; ++i){
			try {
				Method readMethod = null;
				Method writeMethod = null;
				if(!getters.isEmpty()){
					Method getterMethod = beanType.getMethod(getters.get(i));
					getterMethodBuilder.add(getterMethod);
					try {
						readMethod = beanType.getMethod("read" + getters.get(i).replaceFirst("(is|get)", ""));
					} catch (NoSuchMethodException e) {
					}
					readMethodBuffer.add(readMethod);
					if(null != readMethod) {
						if(isJdbcType(fieldTypeOf(i))) {
							jdbcTypeBuilder.add(fieldTypeOf(i));
						}else {
							if(fieldTypeOf(i).equals(readMethod.getReturnType())) {
								jdbcTypeBuilder.add(getterMethod.getReturnType());
							}else {
								jdbcTypeBuilder.add(readMethod.getReturnType());
							}
						}
					}else {
						jdbcTypeBuilder.add(getterMethod.getReturnType());
					}
				}
				if(!setters.isEmpty()){
					setterMethodBuilder.add(beanType.getMethod(setters.get(i), columnTypes[i]));
					if(null != readMethod) {
						try {
							writeMethod = beanType.getMethod("write" + setters.get(i).replaceFirst("set", ""),readMethod.getReturnType());
						} catch (NoSuchMethodException e) {
						}
					}
					writeMethodBuffer.add(writeMethod);
				}
			} catch (Exception e) {
				Throwables.throwIfUnchecked(e);
				throw new RuntimeException(e);
			}
		}
		this.getterMethods = getterMethodBuilder.build();
		this.setterMethods = setterMethodBuilder.build();
		this.readMethods = Collections.unmodifiableList(readMethodBuffer);
		this.writeMethods = Collections.unmodifiableList(writeMethodBuffer);
		this.jdbcTypes = jdbcTypeBuilder.build();
		this.columnCodecs = new ColumnCodec[columnCount];
		this.columnJsonSerializers = new ObjectSerializer[columnCount];
		this.visibilities= buildVisibilities();
		if(!columnJavaNames.isEmpty()) {
			for(int i=0;i<columnCount;++i) {
				String name = columnJavaNames.get(i);
				try {
					columnCodecs[i] = columnCodecOf(beanType.getDeclaredField(name));
					if(null == columnCodecs[i] && isBaseColumnType(fieldTypes.get(i))) {
						if(isBaseColumnType(fieldTypes.get(i))) {
							/** 有read方法或成员字段类型与对外声明的bean字段类型不相同的情况下自动设置一个基础类型的编解码器 */
							if(!this.columnTypes.get(i).equals(fieldTypes.get(i)) || null != readMethods.get(i)) {
								columnCodecs[i] = BASE_CODEC;
							}
						}
					}
					columnJsonSerializers[i] = jsonSerializeOf(beanType.getDeclaredField(name));
				} catch (Exception e) {
					Throwables.throwIfUnchecked(e);
					throw new RuntimeException(e);
				}
			}
		}
		this.hasAutoUpdateColumn = hasAutoUpdateColumn;
	}
	/**
	 * compatibility for previous version
	 * @param tablename 
	 * @param tableType
	 * @param beanType
	 * @param coreClass
	 * @param managerInterfaceClass
	 * @param columnNames
	 * @param columnJavaNames
	 * @param getters
	 * @param setters
	 * @param columnTypes
	 * @param columnSizes
	 * @param sqlTypes
	 * @param primaryKeyNames
	 * @param junctionTablePkMap
	 * @param lockColumnType
	 * @param lockColumnName
	 * @param foreignKeys
	 * @param indices
	 * @param autoincrement
	 */
	protected RowMetaData(
			String tablename,
			String tableType,
			Class<? extends BaseBean> beanType, 
			String coreClass,
			Class<? extends TableManager<?>> managerInterfaceClass, 
			List<String> columnNames, 
			List<String> columnJavaNames, 
			List<String> getters, 
			List<String> setters, 
			Class<?>[] columnTypes, 
			int[] columnSizes, 
			int[] sqlTypes, 
			List<String> primaryKeyNames, 
			Map<String, String> junctionTablePkMap, 
			Class<?> lockColumnType, 
			String lockColumnName, 
			List<String> foreignKeys, 
			List<String> indices, 
			String autoincrement) {
		this(tablename, tableType, beanType, coreClass, managerInterfaceClass, null, columnNames, null, columnJavaNames, getters, 
				setters, columnTypes, columnSizes, sqlTypes, primaryKeyNames, junctionTablePkMap, lockColumnType, lockColumnName, 
				foreignKeys, null, indices, autoincrement, null);
	}
	private ColumnCodec columnCodecOf(Field field) throws InstantiationException, IllegalAccessException {
		ColumnCodecConfig annot = field.getAnnotation(ColumnCodecConfig.class);
		if(null != annot) {
			if(!ColumnCodec.class.equals(annot.value())) {
				return annot.value().newInstance();
			}
		}else  {
			Class<? extends ColumnCodec> ct = columnCodecOf(field.getType());
			if(null != ct) {
				return ct.newInstance();
			}
		}
		return null;
	}
	/**
	 * find valid {@link ColumnCodecConfig} from input class,
	 * if not found try to find in superclass or interfaces recursively
	 * @param clazz
	 */
	private Class<? extends ColumnCodec> columnCodecOf(Class<?> clazz){
		if(null != clazz) {
			ColumnCodecConfig annot = clazz.getAnnotation(ColumnCodecConfig.class);
			if(null != annot && !ColumnCodec.class.equals(annot.value())) {
				return annot.value();
			}
			for(Class<?> iface: clazz.getInterfaces()) {
				Class<? extends ColumnCodec> ct = columnCodecOf(iface);
				if(null != ct) {
					return ct;
				}
			}
			return columnCodecOf(clazz.getSuperclass());
		}
		return null;
	}
	private ObjectSerializer jsonSerializeOf(Field field) throws InstantiationException, IllegalAccessException {
		JSONField annot = field.getAnnotation(JSONField.class);
		if(null != annot) {
			Class<?> serializrClazz = annot.serializeUsing();
			if(!Void.class.equals(serializrClazz)) {
				return (ObjectSerializer) serializrClazz.newInstance();
			}
		}else  {
			Class<?> ct = jsonSerializerOf(field.getType());
			if(null != ct) {
				return (ObjectSerializer) ct.newInstance();
			}
		}
		return null;
	}
	/**
	 * find valid {@link ColumnCodecConfig} from input class,
	 * if not found try to find in superclass or interfaces recursively
	 * @param clazz
	 */
	@SuppressWarnings("unchecked")
	private Class<?> jsonSerializerOf(Class<?> clazz){
		if(null != clazz) {
			JSONField annot = clazz.getAnnotation(JSONField.class);
			if(null != annot && !Void.class.equals(annot.serializeUsing())) {
				return (Class<? extends ObjectSerializer>) annot.serializeUsing();
			}
			for(Class<?> iface: clazz.getInterfaces()) {
				Class<?> ct = jsonSerializerOf(iface);
				if(null != ct) {
					return ct;
				}
			}
			return jsonSerializerOf(clazz.getSuperclass());
		}
		return null;
	}
	public ObjectSerializer jsonSerializerOf(int columnId) {
	    try{
	        return columnJsonSerializers[columnId];
	    } catch(IndexOutOfBoundsException e){
	        return null;
	    }
	}
	/**
	 * return column name specified by column id
	 * @param columnId column id
	 * @return column name or null if columnId is invalid
	 */
	public String columnNameOf(int columnId){
	    try{
	        return columnNames.get(columnId);
	    } catch(IndexOutOfBoundsException e){
	        return null;
	    }
	}
	/**
	 * return camel-case Java name of column specified by column id
	 * @param columnId column id
	 * @return column name or null if columnId is invalid
	 */
	public String columnJavaNameOf(int columnId){
		try{
			return columnJavaNames.get(columnId);
		} catch(IndexOutOfBoundsException e){
			return null;
		}
	}
	/**
	 * return column full name(with table name,such as tablename.columnname) specified by column id
	 * @param columnId column id
	 * @return column full name or null if columnId is invalid
	 */
	public String fullNameOf(int columnId){
	    try{
	    	if(tablename.startsWith(UNKNOW_TABLENAME)){
	    		return columnNames.get(columnId);
	    	}
	        return tablename + "." + columnNames.get(columnId);
	    } catch(IndexOutOfBoundsException e){
	        return null;
	    }
	}
    /**
     * return column ordinal id(base 0) specified by column name
     * @param column column name or full name,or java field name
     * @return column ordinal id(base 0) or -1 if column name is invalid
     */
    public final int columnIDOf(String column){
    	if(null != column){
    		String prefix = tablename + ".";
    		if(column.startsWith(prefix)){
    			column = column.substring(prefix.length());
    		}
    		return firstNonNull(nameIndexsMap.get(column), 
    				firstNonNull(javaNameIndexsMap.get(column), -1));
    	}
    	return -1;
	}
    
    /**
     * return column ordinal id(base 0) specified by column names
     * @param columns array of column name or full name,or java field name
     * @return array of column ordinal id(base 0) or empty array  if columns is null
     * @see #columnIDOf(String)
     */
    public final int[] columnIDsOf(String... columns){
		return null == columns ? new int[0] : Ints.toArray(Lists.transform(Arrays.asList(columns), COLUMNID_FUN));
	}
    /**
     * return column ordinal id(base 0) specified by column names
     * @param columns collection of column name or full name,or java field name
     * @return array of column ordinal id(base 0) or empty array  if columns is null
     * @see #columnIDOf(String)
     */
    public final int[] columnIDsOf(Collection<String> columns){
		return null == columns ? new int[0] : Ints.toArray(Collections2.transform(columns, COLUMNID_FUN));
	}
    /**
     * return column ordinal id(base 0) specified by column names
     * @param columns collection of column name or full name,or java field name
     * @return array of column ordinal id(base 0) or empty array  if columns is null
     * @see #columnIDOf(String)
     */
    public final int[] columnIDsOf(Iterable<String> columns){        
        return null == columns ? new int[0] : Ints.toArray(Lists.newArrayList(Iterables.transform(columns, COLUMNID_FUN)));
    }
    /**
	 * return column names by column names
	 * @param columnIds array of column id
	 * @return array of column name or empty array  if columnIds is null
	 * @see #columnNameOf(int)
	 */
	public final List<String> columnNamesOf(int... columnIds){
		return null == columnIds 
				? Collections.<String>emptyList() 
				: Lists.transform(Ints.asList(columnIds), COLUMNNAME_FUN);
	}
	/**
	 * return column names by column names
	 * @param columnIds array of column id
	 * @return array of column name or empty array  if columnIds is null
	 * @see #columnNameOf(int)
	 * @since 3.15.0 
	 */
	public final List<String> columnNamesOf(Iterable<Integer>columnIds){
		return null == columnIds 
				? Collections.<String>emptyList() 
				: Lists.newArrayList(Iterables.transform(columnIds, COLUMNNAME_FUN));
	}
	/**
     * @param columnId column id
     * @return java type of column,or NULL if columnId is invalid 
     */
	public Class<?> columnTypeOf(int columnId){
	    try{
	        return columnTypes.get(columnId);
	    } catch(IndexOutOfBoundsException e){
	        return null;
	    }
	}
	/**
	 * @param columnId column id
	 * @return java field type of column,or NULL if columnId is invalid 
	 * @since 3.18.1
	 */
	public Class<?> fieldTypeOf(int columnId){
		if(fieldTypes.isEmpty()) {
			return columnTypeOf(columnId);
		}else {
			try{
				return fieldTypes.get(columnId);
			} catch(IndexOutOfBoundsException e){
				return null;
			}
		}
	}
	/**
     * @param columnId column id
     * @return java type of column for JDBC driver,or NULL if columnId is invalid 
     * @since 3.21.0
     */
	public Class<?> jdbcTypeOf(int columnId){
	    try{
	        return jdbcTypes.get(columnId);
	    } catch(IndexOutOfBoundsException e){
	        return null;
	    }
	}
	/**
	 * @param columnId column id
	 * @return Field instance of column,or NULL if columnId is invalid 
	 * @since 3.17.7
	 */
	public Field fieldOf(int columnId){
	    try{
	        String name = columnJavaNameOf(columnId);
	        return null == name ? null : beanType.getDeclaredField(name);
	    } catch (Exception e) {
            Throwables.throwIfUnchecked(e);
            throw new RuntimeException(e);
        }
	}
	/**
	 * @param column column name
	 * @return java type of column,or NULL if column is invalid 
	 */
	public Class<?> columnTypeOf(String column){
		try{
			return columnTypes.get(columnIDOf(column));
		} catch(IndexOutOfBoundsException e){
			return null;
		}
	}
	/**
	 * @param columnId
	 * @return ColumnCodec instance of NULL if column is invalid 
	 * @since 3.21.0
	 */
	public ColumnCodec columnCodecOf(int columnId) {
		try{
			return columnCodecs[columnId];
		} catch(IndexOutOfBoundsException e){
			return null;
		}
	}
	public boolean isValidColumnID(Integer columnId){
        return null != columnId && columnId >= 0 && columnId < columnCount;
    }
    public boolean isValidColumnName(String column){
        return isValidColumnID(columnIDOf(column));
    }
    public int[] validColumnIDsOrAll(int... fieldList)
    {   
        if (null == fieldList || 0 == fieldList.length){
            return defaultColumnIdList.clone();
        }
        List<Integer> validIds = Lists.newArrayListWithCapacity(64);
        for (int columnId:fieldList) {
            if(isValidColumnID(columnId)){
                // valid column id only 
               validIds.add(columnId);
            }
        }
        return Ints.toArray(validIds);
    }
    public int[] validColumnIDsOrAll(Iterable<Integer>fieldList){
        if (null == fieldList || 0 == Iterables.size(fieldList)){
            return defaultColumnIdList.clone();
        }
        int[] array;
        if(fieldList instanceof Collection){
            array = Ints.toArray(Collections2.filter((Collection<Integer>)fieldList, Predicates.notNull()));            
        }else{
            array = Ints.toArray(Lists.newArrayList(Iterables.filter(fieldList, Predicates.notNull())));
        }
        return validColumnIDsOrAll(array);
    }
    public int[] validColumnIDsOrAllOf(String... fieldList){
        if (null == fieldList || 0 == fieldList.length){
            return defaultColumnIdList.clone();
        }
        return validColumnIDsOrAll(columnIDsOf(fieldList));
    }
    public int[] validColumnIDsOrAllOf(Iterable<String> fieldList){
        if (null == fieldList || 0 == Iterables.size(fieldList)){
            return defaultColumnIdList.clone();
        }
        return validColumnIDsOrAll(columnIDsOf(fieldList));
    }
    
    /**
     * @param columnId column id
     * @return SQL type of column,or throw {@link IllegalArgumentException} if columnId is invalid
     * @see  java.sql.Types
     */
	public int sqlTypeOf(int columnId){
		try{
			return sqlTypes[columnId];
		} catch(IndexOutOfBoundsException e){
			throw new IllegalArgumentException(String.format("INVALID columnID %d",columnId));
		}
	}
	
	/**
	 * @param columnId
	 * @return return true if  columnId is a primary key Id 
	 */
	public boolean isPrimaryKeyId(int columnId){
		if(columnId >= 0 && columnId < columnCount){
			for(int id : primaryKeyIds){
				if(columnId == id){
					return true;
				}
			}
		}
		return false;
	}
	/**
	 * @param column
	 * @return return true if  columnId is a primary key Id 
	 */
	public boolean isPrimaryKey(String column){
		return isPrimaryKeyId(columnIDOf(column));
	}
	public boolean isForeignKeyId(int columnId){
		if(columnId >= 0 && columnId < columnCount){
			for(String fkName : foreignKeys.keySet()){
				int[] fkids = foreignKeyIdArrayOf(fkName);
				for(int id : fkids){
					if(id == columnId){
						return true;
					}
				}
			}
		}
		return false;
	}
	public boolean isForeignKey(String column){
		return isForeignKeyId(columnIDOf(column));
	}
	/**
	 * lazy load
	 */
	private final LoadingCache<String,Boolean> checkLinkedTableCache = CacheBuilder.newBuilder().build(
			new CacheLoader<String,Boolean>(){

				@Override
				public Boolean load(final String tablename) throws Exception {
					return Iterables.tryFind(getJunctionTablePkMap().values(), new Predicate<Object[]>() {

						@Override
						public boolean apply(Object[] input) {
							return tablename.equals(input[0]);
						}
					}).isPresent();
				}});
	
	/**
	 * check if the table specified by tablename is linked table of current table  
	 * @param tablename
	 * @return true if be linked table
	 */
	public boolean isLinkedTable(String tablename){
		try {
			return checkLinkedTableCache.get(tablename);
		} catch (ExecutionException e) {
			Throwables.throwIfUnchecked(e.getCause());
			throw new RuntimeException(e);
		}
	}
	/**
	 * @return junctionTablePkMap
	 */
	public ImmutableMap<String, Object[]> getJunctionTablePkMap() {
		if(junctionTablePkMap == null){
			synchronized (this) {
				if(junctionTablePkMap == null){
					ImmutableMap.Builder<String, Object[]> builder = ImmutableMap.builder();
					for(Entry<String, String> entry:junctionTablePkStrMap.entrySet()){
						String[] values = entry.getValue().split("\\.");
						String foreignTable = values[0];
						int columnId = getMetaData(foreignTable).columnIDOf(values[1]);
						checkArgument(columnId >=0,"INVALID foreign key description %s", entry.getValue());
						builder.put(entry.getKey(), new Object[]{foreignTable,columnId});
					}
					this.junctionTablePkMap = builder.build();
				}
			}
		}
		return junctionTablePkMap;
	}
	
	/**
	 * lazy load
	 */
	private final LoadingCache<String,Map<String, String>> junctionMapCache = CacheBuilder.newBuilder().build(
			new CacheLoader<String,Map<String, String>>(){

				@Override
				public Map<String, String> load(final String linkedTableName) throws Exception {
					Map<String, Object[]> m = Maps.filterValues(getJunctionTablePkMap(), new Predicate<Object[]>(){

						@Override
						public boolean apply(Object[] input) {
							return input[0].equals(linkedTableName);
						}});
					return Maps.transformValues(m, new Function<Object[],String>() {

						@Override
						public String apply(Object[] input) {
							return getMetaData((String)input[0]).fullNameOf((Integer)input[1]);
						}
					});
				}});
	
	public Map<String, String> junctionMapOf(String linkedTableName){
		try {
			return junctionMapCache.get(linkedTableName);
		} catch (ExecutionException e) {
			Throwables.throwIfUnchecked(e.getCause());
			throw new RuntimeException(e);
		}
	}
	/** lazy load */
	private final LoadingCache<String,ImmutableBiMap<Integer,Integer>> foreignKeyIdCache = CacheBuilder.newBuilder().build(
			new CacheLoader<String, ImmutableBiMap<Integer,Integer>>(){

			@Override
			public ImmutableBiMap<Integer, Integer> load(String fkName) throws Exception {
				ForeignKeyMetaData foreignkey = foreignKeys.get(fkName);
				checkArgument(foreignkey != null,"INVALID foreign key name:%s",fkName);
				ImmutableBiMap.Builder<Integer, Integer> builder = ImmutableBiMap.builder();
				for(Entry<String, String> entry:foreignkey.columnMaps.entrySet()){
					builder.put(columnIDOf(entry.getKey()), getMetaData(foreignkey.foreignTable).columnIDOf(entry.getValue()));
				}
				return builder.build();
			}});
	
	/**
	 * 
	 * @param fkName foreign key name
	 * @return map of column id TO foreign table column id
	 */
	public ImmutableBiMap<Integer, Integer> foreignKeyIdMapOf(String fkName){
		try {
			return foreignKeyIdCache.get(fkName);
		} catch (ExecutionException e) {
			Throwables.throwIfUnchecked(e.getCause());
			throw new RuntimeException(e);
		}
	}
	
	private volatile ImmutableList<ForeignKeyMetaData> selfRefKeys = null;	
	public ImmutableList<ForeignKeyMetaData> getSelfRefKeys(){
		if(selfRefKeys == null){
			synchronized (this) {
				if(selfRefKeys == null){
					selfRefKeys = ImmutableList.copyOf(Iterables.filter(foreignKeys.values(), new Predicate<ForeignKeyMetaData>(){
						@Override
						public boolean apply(ForeignKeyMetaData input) {
							return input.selfRef;
						}}));
				}
			}
		}
		return selfRefKeys;
	}
	
	/**
	 * lazy load
	 */
	private final LoadingCache<String,ForeignKeyMetaData> selfRefKeyRnCache = CacheBuilder.newBuilder().build(
			new CacheLoader<String,ForeignKeyMetaData>(){

				@Override
				public ForeignKeyMetaData load(final String readableName) throws Exception {
					return Iterables.find(getSelfRefKeys(),new Predicate<ForeignKeyMetaData>(){

						@Override
						public boolean apply(ForeignKeyMetaData input) {
							return input.readableName.equals(readableName);
						}});
				}});
	
	public ForeignKeyMetaData getSelfRefKeyByRn(String readableName){
		try {
			return selfRefKeyRnCache.getUnchecked(readableName);
		} catch (UncheckedExecutionException e) {
			if( e.getCause() instanceof NoSuchElementException){
				throw new RuntimeException(logString("INVALID foreign key readableName %s",readableName));
			}
			throw e;
		}
	}
	/**
	 * lazy load
	 */
	private final LoadingCache<String,int[]> foreignKeyIdArrayCache = CacheBuilder.newBuilder().build(
			new CacheLoader<String,int[]>(){

				@Override
				public int[] load(String fkName) throws Exception {
					ForeignKeyMetaData foreignkey = checkNotNull(foreignKeys.get(fkName),"INVALID foreign key name:%s",fkName);
					return foreignkey.foreignKeyIdArray(COLUMNID_FUN);
				}});
	
	public int[] foreignKeyIdArrayOf(String fkName){
		try {
			return foreignKeyIdArrayCache.get(fkName);
		} catch (ExecutionException | UncheckedExecutionException e) {
			Throwables.throwIfUnchecked(e.getCause());
			throw new RuntimeException(e);
		}
	}
	
	public ForeignKeyMetaData getForeignKey(String fkName){
		return checkNotNull(foreignKeys.get(fkName),"INVALID foreign key %s",fkName);
	}
	public ForeignKeyMetaData getForeignKeyByRn(String readableName){
		return checkNotNull(foreignKeysRn.get(readableName),"INVALID foreign key readableName %s",readableName);
	}
	public List<ForeignKeyMetaData> foreignKeysOf(final String foreignTable){
		Iterable<ForeignKeyMetaData> found = Iterables.filter(foreignKeys.values(), 
				new Predicate<ForeignKeyMetaData>(){
					@Override
					public boolean apply(ForeignKeyMetaData input) {
						return input.foreignTable.equals(foreignTable);
					}});
		return Lists.newArrayList(found);
	}
	
	public ImmutableList<ForeignKeyMetaData> getImportedKeys(){
		if(this.importedKeys == null){
			synchronized (this) {
				if(this.importedKeys == null){
					Pattern pattern = Pattern.compile("(\\w+)\\s*\\((.+)\\)\\s*");
					ImmutableList.Builder<ForeignKeyMetaData> builder = ImmutableList.builder();
					for(String importedkey:importedFknames){
						Matcher m = pattern.matcher(importedkey);
						checkArgument(m.matches(),"INVALID imported key(%s),mismatch REGEX %s",importedkey,pattern.pattern());
						String fkname = m.group(1);
						String tablename = m.group(2);
						ForeignKeyMetaData foreignKeyMetaData = RowMetaData.getMetaData(tablename).getForeignKey(fkname);
						builder.add(foreignKeyMetaData);
					}
					this.importedKeys = builder.build();
				}
			}
		}
		return this.importedKeys;
	}
	
	public ForeignKeyMetaData getImportedKey(String fkName){
		if(this.importKeysMap == null){
			synchronized (this) {
				if(this.importKeysMap == null){
					LinkedHashMap<String, ForeignKeyMetaData> map = Maps.newLinkedHashMap(); 
					for(ForeignKeyMetaData key:getImportedKeys()){
						map.put(key.name, key);
					}
					this.importKeysMap = Collections.unmodifiableMap(map);
				}
			}
		}
		return checkNotNull(importKeysMap.get(fkName),"INVALID fkName %s",fkName);
	}

	/** lazy load */
	private volatile ImmutableList<RowMetaData> junctionTables = null;
	private volatile ImmutableMap<Class<?>,RowMetaData> junctionTablesBeantypeMap = null;
	public ImmutableList<RowMetaData> getJunctionTables(){
		if(junctionTables == null){
			synchronized (this) {
				if(junctionTables == null){
					ImmutableList.Builder<RowMetaData> builder = ImmutableList.builder();
					for(ForeignKeyMetaData foreignKey:getImportedKeys()){
						RowMetaData fkdata = getMetaData(foreignKey.ownerTable);
						if(fkdata.isLinkedTable(tablename)){
							builder.add(fkdata);
						}
					}
					junctionTables = builder.build();
					
				}
			}
		}
		return junctionTables;
	}
	
	public ImmutableMap<Class<?>,RowMetaData> getJunctionTablesLinkedBeantypeMap(){
		if(junctionTablesBeantypeMap == null){
			synchronized (this) {
				if(junctionTablesBeantypeMap == null){
					junctionTablesBeantypeMap = Maps.uniqueIndex(getJunctionTables(), new Function<RowMetaData,Class<?>>(){
						@Override
						public Class<?> apply(RowMetaData input) {
							for(Object[] values:input.getJunctionTablePkMap().values()){								
								if(!tablename.equals(values[0])){
									// return bean type of linked table 
									return RowMetaData.getMetaData((String) values[0]).beanType;
								}
							}
							throw new IllegalStateException("NOT FOUND linked table");
						}});
				}
			}
		}
		return junctionTablesBeantypeMap;
	}

	public RowMetaData getJunctionTableFor(Class<?> linkedBeanType){
		return checkNotNull(getJunctionTablesLinkedBeantypeMap().get(linkedBeanType),"NOT FOUND metadata for %s",linkedBeanType);
	}
	public RowMetaData getJunctionTableFor(Type linkedBeanType){
		if(linkedBeanType instanceof Class<?>){
			Class<?> clazz = (Class<?>)linkedBeanType;
			if(clazz.isArray()){
				return getJunctionTableFor(clazz.getComponentType());
			}
			return getJunctionTableFor(clazz);
		}
		checkArgument(linkedBeanType instanceof ParameterizedType,"INVALID TYPE %s",linkedBeanType);
		Type typeArg = ((ParameterizedType)linkedBeanType).getActualTypeArguments()[0];
		return getJunctionTableFor(typeArg);
	}
	private volatile ImmutableList<ForeignKeyMetaData> foreignKeysForListeners;
	/**
	 * @return 返回 所有需要输出foreign key listener的 {@link ForeignKeyMetaData}对象
	 */
	public ImmutableList<ForeignKeyMetaData> getForeignKeysForListener(){
		// double check
		if(foreignKeysForListeners == null){
			synchronized (this) {
				if(foreignKeysForListeners == null){
					Collection<ForeignKeyMetaData> c = Maps.filterEntries(foreignKeys, 
							new Predicate<Entry<String, ForeignKeyMetaData>>(){
								@Override
								public boolean apply(Entry<String, ForeignKeyMetaData> input) {
									ForeignKeyMetaData fk = input.getValue();
									return fk.updateRule.isNoAction() 
											&& !Strings.isNullOrEmpty(fk.deleteRule.eventOfDeleteRule);
								}}).values();
					// 排序输出
					foreignKeysForListeners = ImmutableList.copyOf(Ordering.natural().onResultOf(new Function<ForeignKeyMetaData,String>(){
						@Override
						public String apply(ForeignKeyMetaData input) {
							return input.name;
						}}).sortedCopy(c));
				}
			}
		}
		return foreignKeysForListeners;
	}
	private volatile ImmutableMap<String, IndexMetaData>  uniqueIndeices;
	public ImmutableMap<String, IndexMetaData> getUniqueIndices(){
		// double check
		if(uniqueIndeices == null){
			synchronized (this) {
				if(uniqueIndeices == null){
					uniqueIndeices = ImmutableMap.copyOf(Maps.filterValues(indices, IndexMetaData.UNIQUE_FILTER));
				}
			}
		}
		return uniqueIndeices;
	} 

	public IndexMetaData getIndexChecked(String indexName){
		return checkNotNull(getUniqueIndices().get(indexName),"INVALID indexName %s",indexName);
	}
	public IndexMetaData getIndexCheckedByRn(String readableName){
		return checkNotNull(indicesRn.get(readableName),"INVALID readableName %s",readableName);
	}
	/**
	 * lazy load
	 */
	private final LoadingCache<String,int[]> indexKeyIdArrayCache = CacheBuilder.newBuilder().build(
			new CacheLoader<String,int[]>(){
				@Override
				public int[] load(String indexName) throws Exception {
					return getIndexChecked(indexName).getColumnIds(COLUMNID_FUN);
				}});
	public int[] indexIdArray(String indexName){
		try {
			return indexKeyIdArrayCache.get(indexName);
		} catch (ExecutionException | UncheckedExecutionException e) {
    		if(null != e.getCause()){
    			Throwables.throwIfUnchecked(e.getCause());
    			throw new RuntimeException(e.getCause());
    		}
    		Throwables.throwIfUnchecked(e);
    		throw new RuntimeException(e);
		}
	}
	
	/**
	 * lazy load
	 */
	private final LoadingCache<String,Class<?>[]> indexTypeArrayCache = CacheBuilder.newBuilder().build(
			new CacheLoader<String,Class<?>[]>(){
				@Override
				public Class<?>[] load(String indexName) throws Exception {
					indexIdArray(indexName);
					return getIndexChecked(indexName).getColumnTypes(JDBCTYPE_FUN);
				}});
	public Class<?>[] indexTypeArray(String indexName){
		try {
			return indexTypeArrayCache.get(indexName);
		} catch (ExecutionException | UncheckedExecutionException e) {
    		if(null != e.getCause()){
    			Throwables.throwIfUnchecked(e.getCause());
    			throw new RuntimeException(e.getCause());
    		}
    		Throwables.throwIfUnchecked(e);
    		throw new RuntimeException(e);
		}
	}
    private class RowComparator<B extends BaseBean> implements Comparator<B> {
        /**
         * Holds the column id on which the comparison is performed.
         */
        private final int columnId;
        /**
         * Value that will contain the information about the order of the sort: normal or reversal.
         */
        private final boolean bReverse;

    	private RowComparator(int columnId, boolean bReverse) {
    		checkArgument(columnTypeOf(columnId) != null,"INVALID column id %s",columnId);
    		checkArgument(Comparable.class.isAssignableFrom(columnTypeOf(columnId)),
    				"type of column %s for the field is not supported Comparable",columnNames.get(columnId));
    		this.columnId = columnId;
    		this.bReverse = bReverse;
    	}

    	@SuppressWarnings("unchecked")
    	@Override
    	public int compare(B o1, B o2) {
    		int iReturn = 0;
    		Object v1= o1.getValue(columnId);
    		Object v2= o2.getValue(columnId);
    		if(v1 ==null && v2 !=null){
    			iReturn = -1;
    		}else if(v1 ==null && v2 ==null){
                iReturn = 0;
            }else if(v1 !=null && v2 ==null){
                iReturn = 1;
            }else{
            	iReturn = ((Comparable<Object>)v1).compareTo(v2);
            }
    		return bReverse ? (-1 * iReturn) : iReturn;
    	}
    }
    
    public <B extends BaseBean> Comparator<B> comparatorOf(int columnId,boolean bReverse){
    	return new RowComparator<B>(columnId,bReverse);
    }
    
	/**
	 * 表名--{@link RowMetaData}映射
	 */
	public static final Map<String, RowMetaData> tableMetadata = new ConcurrentHashMap<>(); 
	/**
	 * 表记录对象--{@link RowMetaData}映射
	 */
	private static final Map<Class<?>, RowMetaData> beanTypeMetadata = new HashMap<>();
	
	/**
	 * SPI(Service Provider Interface)机制加载 {@link IRowMetaData}所有实例
	 * @return 表名和 {@link RowMetaData}实例的映射对象 
	 */
	private static HashMap<String, RowMetaData> loadRowMetaData() {		
		Iterator<IRowMetaData> itor =  SPIUtils.serviceLoaderOf(IRowMetaData.class).iterator();
		IRowMetaData instance;
		HashMap<String, RowMetaData> m = new HashMap<>();
		while(itor.hasNext()){
			try {
				instance = itor.next();
			} catch (ServiceConfigurationError e) {
				// 实例初始化失败输出错误日志后继续循环
				SimpleLog.log(e.getMessage());
				continue;
			}
			if(instance instanceof RowMetaData){
				RowMetaData rowMetaData = (RowMetaData)instance;
				m.put(rowMetaData.tablename, rowMetaData);
			}
		}
		return m;
	}
	/**
	 * 注入{@link RowMetaData}数据
	 * @param tableMetadata 表名--{@link RowMetaData}映射
	 */
	public synchronized static void injectTableMetaData(Map<String, RowMetaData> tableMetadata){
		RowMetaData.tableMetadata.putAll(checkNotNull(tableMetadata,"tableMetadata is null"));
		RowMetaData.beanTypeMetadata.putAll(Maps.uniqueIndex(tableMetadata.values(), 
				new Function<RowMetaData,Class<?>>(){
			
				@Override
				public Class<?> apply(RowMetaData input) {
					return input.beanType;
				}}));
	}
	
	/**
	 * 返回所有 {@link RowMetaData}实例
	 */
	public static final List<RowMetaData> allMetaDataList() {
		return Lists.newArrayList(tableMetadata.values());
	}
	/**
	 * 根据表名返回对应的 {@link RowMetaData}实例
	 * @param tablename 表名
	 * @return {@link RowMetaData}实例,找不到时抛出异常
	 */
	public static final RowMetaData getMetaData(String tablename) {
		RowMetaData metaData =  tableMetadata.get(tablename);
		return checkNotNull(metaData,"INVALID TABLE NAME %s",tablename);
	}
	/**
	 * 根据表名返回对应的 {@link RowMetaData}实例
	 * @param tablename 表名
	 * @return {@link RowMetaData}实例,找不到时返回{@code null}
	 */
	public static final RowMetaData getMetaDataUnchecked(String tablename) {
		return tableMetadata.get(tablename);
	}
	/**
	 * 根据beanType返回对应的 {@link RowMetaData}实例
	 * @param beanType 表名
	 * @return {@link RowMetaData}实例,找不到时抛出异常
	 * @see #getMetaData(Class, boolean)
	 */
	public static final RowMetaData getMetaData(Class<?> beanType) {
		return getMetaData(beanType,false);
	}
	/**
	 * 根据beanType返回对应的 {@link RowMetaData}实例
	 * @param beanType 表名
	 * @param matchSubclass 是否匹配子类，为{@code true}时，如果找不到会尝试查找 beanType 的子类，返回对应的{@link RowMetaData}实例
	 * @return {@link RowMetaData}实例,找不到时抛出异常
	 * @see Sql2javaSupport#findSubClass(Iterable, Class)
	 * @since 3.32.6
	 */
	public static final RowMetaData getMetaData(Class<?> beanType, boolean matchSubclass) {
		RowMetaData metaData = beanTypeMetadata.get(beanType);
		if (null == metaData && matchSubclass) {
			Class<?> subType = findSubClass(beanTypeMetadata.keySet(), beanType);
			if (null != subType) {
				metaData = beanTypeMetadata.get(subType);
			}
		}
		return checkNotNull(metaData, "INVALID bean type %s", beanType);
	}

	/**
	 * 根据beanType返回对应的 {@link RowMetaData}实例
	 * @param beanType 表名
	 * @return {@link RowMetaData}实例,找不到时返回{@code null}
	 */
	public static final RowMetaData getMetaDataUnchecked(Class<?> beanType) {
		return  beanTypeMetadata.get(beanType);
	}
	/** lazy load */
	private final static  LoadingCache<String,RowMetaData> metaDataClassNameCache = CacheBuilder.newBuilder().build(
			new CacheLoader<String, RowMetaData>(){

			@Override
			public RowMetaData load(String beanClassSimpleName) throws Exception {
				// 允许以<alias>SimpleName 形式指定alias
				Pattern pattern = Pattern.compile("^(?:<(.*)>)?(.*)$");
				Matcher macher = pattern.matcher(Strings.nullToEmpty(beanClassSimpleName));
				checkArgument(macher.find(), "INVALID beanClassSimpleName format " + beanClassSimpleName);
				final String alias = macher.group(1);
				final String simpleName = macher.group(2);
				RowMetaData found = Iterables.find(beanTypeMetadata.values(), new Predicate<RowMetaData>() {

					@Override
					public boolean apply(RowMetaData input) {
						// 匹配类名和alias
						return input.beanType.getSimpleName().equals(simpleName) && Objects.equal(alias, input.alias);
					}
				});
				return found;
			}});
	public static final RowMetaData getRowMetaDataByBeanClassName(String beanClassSimpleName, String alias){
		try {
			if(!Strings.isNullOrEmpty(alias)){
				beanClassSimpleName = "<" + alias + ">" + beanClassSimpleName;
			}
			return metaDataClassNameCache.getUnchecked(beanClassSimpleName);
		} catch (UncheckedExecutionException e) {
			throw new IllegalArgumentException(e.getCause());
		}
	}
	public static final ForeignKeyMetaData getForeignKey(String importeBeanName,String readableName, String alias){
		RowMetaData importedTableMetaData = getRowMetaDataByBeanClassName(importeBeanName, alias);
		return importedTableMetaData.getForeignKeyByRn(readableName);
	}
	/** lazy load */
	private static final LoadingCache<String,RowMetaData> coreClassNameCache = CacheBuilder.newBuilder().build(
			new CacheLoader<String, RowMetaData>(){

			@Override
			public RowMetaData load(final String coreClassName) throws Exception {
				// 允许以<alias>SimpleName 形式指定alias
				Pattern pattern = Pattern.compile("^(?:<(.*)>)?(.*)$");
				Matcher macher = pattern.matcher(Strings.nullToEmpty(coreClassName));
				checkArgument(macher.find(), "INVALID beanClassSimpleName format " + coreClassName);
				final String alias = macher.group(1);
				final String coreName = macher.group(2);
				return Iterables.find(tableMetadata.values(), new Predicate<RowMetaData>() {

					@Override
					public boolean apply(RowMetaData input) {
						// 匹配类名和alias
						return input.coreClass.equals(coreName) && Objects.equal(alias, input.alias);
					}
				});
			}});
	public static final RowMetaData getRowMetaDataByCoreClassName(String coreClassName, String alias){
		try {
			if(!Strings.isNullOrEmpty(alias)){
				coreClassName = "<" + alias + ">" + coreClassName;
			}
			return coreClassNameCache.getUnchecked(coreClassName);
		} catch (UncheckedExecutionException e) {
			throw new IllegalArgumentException(logString("INVALID coreClassName %s",coreClassName));
		}
	}
	private RowMeta getRowMetaAnnot() {
		if(null==rowMetaAnnot) {
			synchronized (this) {
				if(null== rowMetaAnnot) {
					MergedAnnotation<RowMeta> annnots = TypeMappedAnnotations.from(getClass()).get(RowMeta.class);
					rowMetaAnnot = annnots.isPresent()
							? annnots.synthesize()
							: AnnotationUtils.synthesizeAnnotation(RowMeta.class);
				}
			}
		}
	 	return rowMetaAnnot;
	}
	private ImmutableList<ColumnVisibility> buildVisibilities(){
		final ImmutableSet<String> localScopeColumns = ImmutableSet.copyOf(getRowMetaAnnot().localScopeColumns());
		final ImmutableSet<String> thriftScopeColumns = ImmutableSet.copyOf(getRowMetaAnnot().thriftScopeColumns());
		final ImmutableSet<String> jsonScopeColumns = ImmutableSet.copyOf(getRowMetaAnnot().jsonScopeColumns());
		return FluentIterable.from(columnNames).transform(new Function<String, ColumnVisibility>() {

			@Override
			public ColumnVisibility apply(String input) {
				if(localScopeColumns.contains(input)) {
					return ColumnVisibility.LOCAL;
				}else if(thriftScopeColumns.contains(input)) {
					return ColumnVisibility.THRIFT;
				}else if(jsonScopeColumns.contains(input)) {
					return ColumnVisibility.JSON;
				}
				return ColumnVisibility.DEFAULT;
			}
		}).toList();
	}
    /**
	 * @param columnId
	 * @since 3.32.0
	 */
	public ColumnVisibility visibilityOf(int columnId) {
		try{
			return visibilities.get(columnId);
		} catch(IndexOutOfBoundsException e){
			return null;
		}
	}
	/**
	 * @param column
	 * @since 3.32.0
	 */
	public ColumnVisibility visibilityOf(String column) {
		return visibilityOf(columnIDOf(column));
	}
	/**
	 * @param <T>
	 * @param visibility
	 * @param transfomer
	 * @since 3.32.0
	 */
	public <T> LinkedHashSet<T> columnGroupByVisibility(ColumnVisibility visibility,Function<Integer,T> transfomer){
		LinkedHashSet<T> list = new LinkedHashSet<>(columnCount);
		for(int i=0;i<columnCount;++i) {
			if(visibility.match(visibilities.get(i))) {
				list.add(transfomer.apply(i));
			}
		}
		return list;
	}
	/**
	 * @param visibility
	 * @since 3.32.0
	 */
	public LinkedHashSet<String> columnNamesWith(ColumnVisibility visibility){
		return columnGroupByVisibility(visibility,COLUMNNAME_FUN);
	}
	/**
	 * @param visibility
	 * @since 3.32.0
	 */
	public LinkedHashSet<Integer> columnIDsWith(ColumnVisibility visibility){
		return columnGroupByVisibility(visibility,Functions.<Integer>identity());
	}
	/**
     * @since 3.32.6
     */
	@SuppressWarnings("unchecked")
	public <M extends TableManager<? extends BaseBean>> M getManagerUnchecked() {
		// DOUBLE CHECK
		if (null == manager) {
			synchronized (this) {
				if (null == manager) {
					TableManagerProvider provider = TableManagerProviders.getInstance();
					if (null == provider) {
						manager = Optional.absent();
					}
					manager = Optional.<TableManager<? extends BaseBean>>of(provider.managerOf(beanType));
				}
			}
		}
		return (M) manager.orNull();
	}
    /**
     * @since 3.20.2
     */
    @SuppressWarnings("unchecked")
    public <M extends TableManager<? extends BaseBean>> M getManager(){
    	TableManager<? extends BaseBean> manager = getManagerUnchecked();
    	if(null == manager) {
    		throw new UnsupportedOperationException("UNSUPPORTED OPERATION");
    	}
    	return (M) manager;
    }
    /**
     * create a new instance of {@link #beanType}
     * @since 3.20.3
     */
    @SuppressWarnings("unchecked")
	public <B extends BaseBean>B createBean() {
		try {
			return (B) beanType.newInstance();
		} catch (Exception e) {
			Throwables.throwIfUnchecked(e);
			throw new RuntimeException(e);
		}
    }
    /**
     * @since 3.32.6
     */
    public Set<Integer> getPrimaryKeyIdAsSet(){
    	if(primaryKeyIdSet == null) {
    		synchronized (this) {
    			if(primaryKeyIdSet == null) {
    				primaryKeyIdSet = ImmutableSet.copyOf(Ints.asList(primaryKeyIds));
    			}
			}
    	}
    	return primaryKeyIdSet;
    }
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((columnNames == null) ? 0 : columnNames.hashCode());
		result = prime * result + ((tableType == null) ? 0 : tableType.hashCode());
		result = prime * result + ((tablename == null) ? 0 : tablename.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (!(obj instanceof RowMetaData)) {
			return false;
		}
		RowMetaData other = (RowMetaData) obj;
		if (columnNames == null) {
			if (other.columnNames != null) {
				return false;
			}
		} else if (!columnNames.equals(other.columnNames)) {
			return false;
		}
		if (tableType == null) {
			if (other.tableType != null) {
				return false;
			}
		} else if (!tableType.equals(other.tableType)) {
			return false;
		}
		if (tablename == null) {
			if (other.tablename != null) {
				return false;
			}
		} else if (!tablename.equals(other.tablename)) {
			return false;
		}
		return true;
	}
	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		builder.append("RowMetaData [tablename=");
		builder.append(tablename);
		builder.append(", tableType=");
		builder.append(tableType);
		builder.append(", columnNames=");
		builder.append(columnNames);
		builder.append("]");
		return builder.toString();
	}

	static{
		// 注入SPI加载的{@link RowMetaData}
		injectTableMetaData(loadRowMetaData());
	}

}